package com.parsechina.hpush.broker.internal.subscriptions;


import com.parsechina.hpush.broker.spi.ISessionsStore;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @author linfeng
 */
public class TreeNode {

    Token token;
    List<TreeNode> treeNodeChildren = new ArrayList<>();

    Set<ISessionsStore.ClientTopicRelation> clientTopicRelations = new HashSet<>();

    Token getToken() {
        return token;
    }

    void setToken(Token topic) {
        token = topic;
    }

    void addSubscription(ISessionsStore.ClientTopicRelation s) {
        clientTopicRelations.add(s);
    }

    void addChild(TreeNode child) {
        treeNodeChildren.add(child);
    }

    /**
     * Creates a shallow copy of the current node.
     * Copy the token and the children.
     */
    TreeNode copy() {
        final TreeNode copy = new TreeNode();
        copy.treeNodeChildren = new ArrayList<>(treeNodeChildren);
        copy.clientTopicRelations = new HashSet<>(clientTopicRelations);
        copy.token = token;
        return copy;
    }

    /**
     * Search for children that has the specified token, if not found return
     * null;
     */
    TreeNode childWithToken(Token token) {
        for (TreeNode child : treeNodeChildren) {
            if (child.getToken().equals(token)) {
                return child;
            }
        }

        return null;
    }

    void updateChild(TreeNode oldChild, TreeNode newChild) {
        treeNodeChildren.remove(oldChild);
        treeNodeChildren.add(newChild);
    }

    Collection<ISessionsStore.ClientTopicRelation> subscriptions() {
        return clientTopicRelations;
    }

    public void remove(ISessionsStore.ClientTopicRelation clientTopicRelation) {
        clientTopicRelations.remove(clientTopicRelation);
    }

    void matches(Queue<Token> tokens, List<ISessionsStore.ClientTopicRelation> matchingSubs) {
        Token t = tokens.poll();

        //check if t is null <=> tokens finished
        if (t == null) {
            matchingSubs.addAll(clientTopicRelations);
            //check if it has got a MULTI child and add its subscriptions
            for (TreeNode n : treeNodeChildren) {
                if (n.getToken() == Token.MULTI || n.getToken() == Token.SINGLE) {
                    matchingSubs.addAll(n.subscriptions());
                }
            }

            return;
        }

        //we are on MULTI, than add subscriptions and return
        if (token == Token.MULTI) {
            matchingSubs.addAll(clientTopicRelations);
            return;
        }

        for (TreeNode node : treeNodeChildren) {
            if (node.getToken().match(t)) {
                //Create a copy of token, else if navigate 2 sibling it
                //consumes 2 elements on the queue instead of one
                node.matches(new ConcurrentLinkedQueue<>(tokens), matchingSubs);
                //TODO don't create a copy n.matches(tokens, matchingSubs);
            }
        }
    }

    /**
     * Return the number of registered subscriptions
     */
    int size() {
        int res = clientTopicRelations.size();
        for (TreeNode child : treeNodeChildren) {
            res += child.size();
        }
        return res;
    }

    /**
     * Create a copied subtree rooted on this node but purged of clientId's subscriptions.
     */
    TreeNode removeClientSubscriptions(String clientID) {
        //collect what to delete and then delete to avoid ConcurrentModification
        TreeNode newSubRoot = copy();
        List<ISessionsStore.ClientTopicRelation> subsToRemove = new ArrayList<>();
        for (ISessionsStore.ClientTopicRelation topicCouple : newSubRoot.clientTopicRelations) {
            if (topicCouple.clientId.equals(clientID)) {
                subsToRemove.add(topicCouple);
            }
        }

        for (ISessionsStore.ClientTopicRelation clientTopicRelation : subsToRemove) {
            newSubRoot.clientTopicRelations.remove(clientTopicRelation);
        }

        //go deep
        List<TreeNode> newChildren = new ArrayList<>(newSubRoot.treeNodeChildren.size());
        for (TreeNode child : newSubRoot.treeNodeChildren) {
            newChildren.add(child.removeClientSubscriptions(clientID));
        }
        newSubRoot.treeNodeChildren = newChildren;
        return newSubRoot;
    }
}
